π SPACE Client SDK for React
The SPACE React SDK provides fully-typed React/TypeScript components, hooks, and utilities to seamlessly integrate your web apps with SPACE.
With this SDK you can:
- β‘ Connect a React application to a SPACE instance (HTTP + WebSocket).
- π Generate and manage Pricing Tokens directly in the client.
- π§© Activate/deactivate UI components declaratively with the
<Feature>component. - π Subscribe to SPACE pricing events to keep your UI in sync.
This SDK is intended for research and experimentation. For production usage, see License & Disclaimer.
β οΈ Important Noteβ
- Feature evaluation is always based on the Pricing Token.
space-react-clientdoes not call SPACE directly to check features; instead, it uses the evaluation results stored in the Pricing-Token loaded inTokenService. (See SPACE communication protocol for more details on how pricing tokens work.)
π¦ Installationβ
Install with your package manager of choice:
npm install space-react-client
# or
yarn add space-react-client
# or
pnpm add space-react-client
Peer dependencies:
react >= 18react-dom >= 18
β‘ Quick Startβ
The minimal setup to connect to SPACE, load a userβs Pricing Token, and render UI conditionally with <Feature>.
1. Wrap your app with SpaceProviderβ
import React from 'react';
import { createRoot } from 'react-dom/client';
import { SpaceProvider } from 'space-react-client';
import App from './App';
const config = {
url: 'http://localhost:5403', // Your SPACE instance URL
apiKey: 'YOUR_API_KEY', // API key issued by SPACE
allowConnectionWithSpace: true,
};
createRoot(document.getElementById('root')!)
.render(
<SpaceProvider config={config}>
<App />
</SpaceProvider>
);
Setting allowConnectionWithSpace: false disables all connections to SPACE.
This means you can still evaluate features from a token, but event listeners (e.g., pricing_created, pricing_archived, etc.) as well as methods like setUserId and generateUserPricingToken will not work.
2. Identify the user and load a Pricing Tokenβ
import { useEffect } from 'react';
import { useSpaceClient } from 'space-react-client';
export function App() {
const spaceClient = useSpaceClient();
useEffect(() => {
spaceClient.setUserId('user-123')
.then(() => console.log("User's pricing token set"))
.catch(console.error);
// Listen for SPACE sync events
const onSync = () => console.log('Connected & synchronized with SPACE');
spaceClient.on('synchronized', onSync);
return () => spaceClient.off('synchronized', onSync);
}, [spaceClient]);
return <YourComponent />;
}
3. Gate UI with <Feature>β
import { Feature, On, Default, Loading, ErrorFallback } from 'space-react-client';
export function OnlineVisitsButton() {
return (
<Feature id="petclinic-visits">
<On>
{/* Rendered when feature is enabled */}
<button>Start online visit</button>
</On>
<Default>
{/* Rendered when feature is disabled */}
<button disabled>Upgrade to enable online visits</button>
</Default>
<Loading>
{/* Rendered while evaluating */}
<span>Checking your planβ¦</span>
</Loading>
<ErrorFallback>
{/* Rendered on error */}
<span>Could not verify your access permission to online visits.</span>
</ErrorFallback>
</Feature>
);
}
π Alternative: Token-only mode (no live connection)β
Set allowConnectionWithSpace: false to disable the WebSocket client.
You can then inject a Pricing Token from your backend:
import { useEffect } from 'react';
import { usePricingToken } from 'space-react-client';
export function InjectTokenFromServer() {
const tokenService = usePricingToken();
useEffect(() => {
fetch('/api/my-pricing-token')
.then(res => res.text()) // token as string
.then(token => tokenService.updatePricingToken(token))
.catch(console.error);
}, [tokenService]);
return <YourComponent />;
}
π API Referenceβ
Providersβ
-
SpaceProvider({ config, children })
Initializes the client and provides context.- Props:
config: SpaceConfigurationurl: stringβ SPACE instance URLapiKey: stringβ Authentication key emitted by SPACEallowConnectionWithSpace: booleanβ Iffalse, event listeners take no effect (default:true)
children: React.ReactNode
- Props:
Hooksβ
-
useSpaceClient(): SpaceClient
Returns the connectedSpaceClientinstance. Throws if not available. -
useTokenService(): TokenService
Returns theTokenServiceinstance that is managing the lifecycle of pricing tokens. Available even if live connection is disabled. -
usePricingTokenPayload(): Record<string, any> | null
Returns the parsed payload of the current pricing token, ornullif none is set or invalid. Could be useful to maintain the UI in sync with pricing token.
UI Componentsβ
-
<Feature id="feature-id">β¦</Feature>
Declarative feature gating.- Subcomponents:
<On>β Rendered when feature evaluates totrue.<Default>β Rendered when feature evaluates tofalse.<Loading>β Rendered while evaluating.<ErrorFallback>β Rendered on errors (invalid id, expired token, etc).
- Subcomponents:
The feature-id is a string in the format saasName-featureName, where saasname is always lowercase.
For example, to reference the pets feature from PetClinic, the resulting feature-id would be: petclinic-pets.
Client APIβ
SpaceClient is instantiated by SpaceProvider.
Attributes:
- httpUrl:
stringβ Configured base URL of the SPACE instance. π Example:http://localhost:5403/api/v1 - wsUrl:
stringβ Configured WebSocket URL of the SPACE instance. π Example:ws://localhost:5403/api/v1/events - apiKey:
stringβ Configured API key for authentication. π Example:6a0f4f1093c1e95616efd61b69b15f90f1b25953a4201961995b8a89035aac72 - token:
TokenServiceβ Instance of the Token Service being employed. could be useful when working inallowConnectionWithSpace: truemode to not need theuseTokenServicehook.
Methods:
on(event, callback)β Listen to SPACE events.off(event?, callback?)β Remove listeners. If no args, removes all listeners.setUserId(userId)β Set user for evaluations, generates a pricing token, and stores it.generateUserPricingToken()β Generate and return a fresh Pricing Token for the user configured withsetUserId. It does not store the token.
β Supported Events
synchronizedβ Client is connected and synced with SPACE.pricing_createdβ A new pricing was added.pricing_activatedβ A pricing moved from archived β active.pricing_archivedβ A pricing moved from active β archived.service_disabledβ A service was disabled.errorβ Connection or processing error.
All events (except synchronized and error) include the following object:
{
serviceName: string; // REQUIRED: The name of the service that triggered the event
pricingVersion?: string; // OPTIONAL: The version of the pricing involved
}
Token Serviceβ
Methods:
update(token: string): voidβ Validates & stores a pricing token.getPayload(): Record<string, any> | nullβ Return parsed token payload.getKey(key: string): any | nullβ Search the key within the token payload and returns its value.evaluateFeature(featureId: string): boolean | nullβ Returnstrue | false | null.subscribe(listener: () => void): () => voidβ Subscribe to token changes. Returns an unsubscribe function.
Token expectations:
exp: numberβ UNIX expiration.features: Record<string, { eval: boolean; limit?: number | null; used?: number | null }>pricingContext: Record<string, {features: Record<string, string|boolean>, usageLimits: Record<string, number|boolean>}> | nullβ Optional object containing the pricingContext considered for evaluation. It represents the configuration the user has access to.subscriptionContext: Record<string, number> | nullβ Optional object containing the subscriptionContext considered for evaluation.
π‘οΈ Security Considerationsβ
- Do not expose SPACE API keys in production.
Instead, issue Pricing Tokens from your backend and deliver them to the client. - Tokens are validated client-side with the
expclaim. Rotate or shorten TTLs as needed.
βοΈ Development & Toolingβ
The project uses the following main tools and technologies:
π License & Disclaimerβ
This project is licensed under the MIT License. See LICENSE.
This SDK is part of ongoing research in pricing-driven devops. It is still in an early stage and not intended for production use.
β FAQβ
Q: Do I need SPACE running to use this?
A: Yes, for live connectivity. In token-only mode, you just need a Pricing Token from your backend.
Q: Why does useSpaceClient throw?
A: Likely because youβre outside SpaceProvider, or allowConnectionWithSpace is false.
Q: Whatβs the format for feature IDs?
A: A feature id must:
- Always include a dash (
-). - Match exactly the keys present in the Pricing Token payload.
The format is built internally as: saasName-featureName, where saasname is always lowercase.
For example, if you want to instantiate the feature pets from the SaaS PetClinic, the feature id would be: petclinic-pets.